<?php
/*
 * @Descripttion: 文件操作相关控制器，包括上传，移动，删除等
 * @version:
 * @Author: ewgof
 * @Date: 2019-09-29 15:17:54
 * @LastEditors: ewgof
 * @LastEditTime: 2020-12-04 15:49:24
 */
namespace cw;

use app\common\service\Image;

class File
{
    protected $oldname;            // 原文件名
    protected $tmpname;             // 上传的临时文件名
    protected $newname;             // 随机命名后的新文件名
    protected $israndname;          // 是否随机重命名文件,true(默认)重命名,false不随机重命名

    protected $path = './upload';       // 默认上传目录在当前目录下的upload目录
    protected $max_size = 2097152;   // 允许上传的大小,2M以内
    protected $type = array('png', 'jpg', 'jpeg', 'gif', 'bmp');  // 允许上传的类型
    protected $filetype;                // 当前上传文件的类型(文件后缀名)
    protected $filesize;                // 当前上传文件大小
    protected $errornum = 0;            // 错误号
    protected $errormsg;                // 错误信息
    protected $mime = '';               // 当前上传的文件 mime 类型
    protected $index = 'pic';           // 文件信息数组中的key

    /**
     * 构造方法决定是否给上传文件重命名为随机文件名
     * @param boolean $bool [description]
     */
    public function __construct($bool = true)
    {
        $this->set('israndname', $bool);
    }

    /**
     * 设置设置成员属性; 注意:若是设置文件类型的值,可以传入一个一维数组,其余的属性值不可以是数组;
     * 可以设置的属性有: $israndname, $path, $max_size, $type,$index;
     * 本方法可以连续多次调用以设置多个属性,或者使用"->"访问符连贯调用本方法以同时设置多个属性;
     * 或者单次调用设置单个属性
     * @param  string $key 属性名
     * @param  string|array|int $val 属性值
     * @return object      本类实例化的对象本身
     */
    public function set($key, $val)
    {
        $key = strtolower($key);
        $className = get_class($this);
        $varArr = get_class_vars($className);

        if (array_key_exists($key, $varArr)) {
            $this->setOption($key, $val);
        }

        return $this;
    }


    /**
     * 调用本方法执行文件上传
     * @param integer $number 多图片上传时，限制上传图片的数量，默认为 0 ，不限制
     * @return array 包含操作结果代码和上传文件信息的数组:1200表示上传成功,1500表示失败;如果文件上传成功,可以返回
     * 已经上传文件的路径和文件名;失败则返回错误提示信息
     */
    public function upload(array $fileinfo, $number = 0, $compress = true)
    {
        if (empty($fileinfo)) {
            $this->setOption('errornum', 4);
            $msg = $this->getErrorMsg();
            return ['code' => 1500, 'msg' => $msg];
        }

        $field = $this->index;

        $name = $fileinfo[$field]['name'];
        $tmp_name = $fileinfo[$field]['tmp_name'];
        $size = $fileinfo[$field]['size'];
        $error = $fileinfo[$field]['error'];

        if (!is_array($name)) {
            // 单文件上传

            $this->setFilePro($name, $tmp_name, $size, $error);

            // 验证错误号
            if ($this->errornum !== 0) {
                // 上传文件出错
                $errorMsg = $this->getErrorMsg();
                return ['code' => 1500, 'msg' => $errorMsg];
            }

            // 验证上传文件类型
            $res = $this->checkType();
            if (!$res) {
                // 上传文件出错
                $errorMsg = $this->getErrorMsg();
                return ['code' => 1500, 'msg' => $errorMsg];
            }

            // 验证文件大小
            if (!$this->checkSize()) {
                // 上传文件出错
                $errorMsg = $this->getErrorMsg();
                return ['code' => 1500, 'msg' => $errorMsg];
            }

            // 检查路径是否合法
            $checkPathRes = $this->checkPath();

            if ($checkPathRes === 1) {
                $errorMsg =  $this->getErrorMsg();
                return ['code' => 1500, 'msg' => $errorMsg];
            } elseif ($checkPathRes === 2) {
                // 目录不存在,创建目录
                mkdir($this->path, 0755, true);
            }

            // 设置新的文件名(重命名后的随机文件名)
            $this->setNewName();

            // 设置文件上传后的文件名
            $filename = $this->setUpFileName();
            $moveRes = rename($this->tmpname, $filename);

            if ($moveRes) {
                if ($compress) {
                    // 压缩图片
                    $this->compress($filename, $size);
                }
                // 文件上传成功
                $resData = [
                    'code' => 1200,
                    'filename' => $filename,
                    'old_name' => $this->oldname,
                    'size' => $this->filesize,
                    'mime' => $fileinfo[$field]['type'],
                ];
            } else {
                // 文件上传失败
                $this->setOption('errornum', 10);
                $msg = $this->getErrorMsg();

                $resData = [
                    'code' => 1500,
                    'msg' => $msg,
                ];
            }
            return $resData;
        } else {
            // 多文件上传走此区间
            
            // 检查路径是否合法
            $checkPathRes = $this->checkPath();

            if ($checkPathRes === 1) {
                $errorMsg =  $this->getErrorMsg();
                return ['code' => 1500, 'msg' => $errorMsg];
            } elseif ($checkPathRes === 2) {
                // 目录不存在,创建目录
                $r = mkdir($this->path, 0755);
            }


            if ($number) {
                $forNum = min($number, count($name));
            } else {
                $forNum = count($name);
            }
            // var_dump($forNum);die;

            for ($i = 0; $i < $forNum; $i++) {
                
                // echo '<pre>';
                // print_r($fileinfo);
                // echo '<pre>';
                // exit;
                $this->setFilePro($name[$i], $tmp_name[$i], $size[$i], $error[$i]);

                // 验证错误号
                if ($this->errornum !== 0) {
                    // 上传文件出错
                    $errorMsg = $this->getErrorMsg();
                    $resData[$i] = [
                        'code' => 1500,
                        'msg' => $errorMsg,
                    ];
                    continue;
                }

                // 验证上传文件类型
                $res = $this->checkType();
                if (!$res) {
                    // 上传文件出错
                    $errorMsg = $this->getErrorMsg();
                    $resData[$i] = [
                        'code' => 1500,
                        'msg' => $errorMsg,
                    ];
                    continue;
                }

                // 验证文件大小
                if (!$this->checkSize()) {
                    // 上传文件出错
                    $errorMsg = $this->getErrorMsg();
                    $resData[$i] = [
                        'code' => 1500,
                        'msg' => $errorMsg,
                    ];
                    continue;
                }

                // 设置新的文件名(重命名后的随机文件名)
                $this->setNewName();

                // 设置文件上传后的文件名
                $filename = $this->setUpFileName();
                $moveRes = $this->rename($this->tmpname, $filename);
                               
                if ($moveRes) {
                    if ($compress) {
                        // 压缩图片
                        $this->compress($filename, $size[$i]);
                    }
                    // 文件上传成功
                    $resData[] = [
                        'code' => 1200,
                        'filename' => $filename,
                        'old_name' => $this->oldname,
                        'size' => $this->filesize,
                        'mime' => $fileinfo[$field]['type'],
                    ];
                } else {
                    // 文件上传失败
                    $this->setOption('errornum', 10);
                    $errorMsg = $this->getErrorMsg();

                    $resData[$i] = [
                        'code' => 1500,
                        'msg' => $errorMsg,
                    ];
                }
            }
            return $resData;
        }
    }


    /**
     * 获取上传错误信息
     * @return string 错误信息
     */
    protected function getErrorMsg()
    {
        $this->setErrorMsg();

        $msg = $this->errormsg;

        return $msg;
    }


    /**
     * 设置成员属性errormsg的值
     */
    protected function setErrorMsg()
    {
        // $tip = '上传文件<span style="color:red;font-size:18px;font-weight:800;">'.$this->oldname.'</span>时出错: ';
        $tip = \str_replace('{%filename%}', $this->oldname, lang(6));
        
        switch ($this->errornum) {
            case 1:
                $tip .= lang(7);
                break;
            case 2:
                $tip .= lang(8);
                break;
            case 3:
                $tip .= lang(9);
                break;
            case 4:
                $tip .= lang(10);
                break;
            case 6:
                $tip .= lang(11);
                break;
            case 7:
                $tip .= lang(12);
                break;
            case 8:
                $tip .= lang(12);
                break;
            case 9:
                $tip .= lang(14);
                break;
            default:
                $tip .= lang(15);
        }

        $this->setOption('errormsg', $tip);
    }


    /**
     * 设置单个属性的属性值
     * @param string $key 属性名
     * @param string|int|array $val 属性值
     */
    protected function setOption($key, $val)
    {
        $this->$key = $val;
    }


    /**
     * 设置随机文件名
     */
    protected function setNewName()
    {
        $randStr = md5(uniqid().mt_rand());
        $this->newname = $randStr.'.'.$this->filetype;
    }


    /**
     * 设置文件相关的属性及其属性值
     * @param string $name     原文件名
     * @param string $tmp_name 上传文件的临时文件名
     * @param int $size     上传文件的大小
     * @param int $error    上传文件错误号
     */
    protected function setFilePro($name, $tmp_name, $size, $error = 0)
    {
        $this->setOption('errornum', $error);
        if ($error) {
            return false;
        } else {
            $this->setOption('oldname', $name);
            $this->setOption('tmpname', $tmp_name);
            $this->setOption('filesize', $size);

            $filetype = strrchr($name, '.');
            $filetype = ltrim($filetype, '.');
            $this->setOption('filetype', $filetype);

            return true;
        }
    }


    /**
     * 判断指定目录是否合法
     */
    protected function checkPath()
    {
        $exists = file_exists($this->path);
        $write = is_writable($this->path);
        if ($exists) {
            if ($write) {
                // 路径合法
                return 0;
            } else {
                // 路径不可写,权限不足
                $this->setOption('errornum', 7);
                return 1;
            }
        } else {
            // 目录不存在
            $this->setOption('errornum', 9);
            return 2;
        }
    }


    /**
     * 检测上传文件的合法性
     * @return boolean
     */
    protected function checkType()
    {
        $bool = in_array($this->filetype, $this->type);
        if ($bool) {
            return true;
        } else {
            $this->setOption('errornum', 8);
            return false;
        }
    }



    /**
     * 检测上传文件的大小是否合法
     * @return boolean
     */
    protected function checkSize()
    {
        if ($this->filesize <= $this->max_size) {
            return true;
        } else {
            $this->setOption('errornum', 1);
            return false;
        }
    }


    /**
     * 设置文件上传后的文件名
     */
    protected function setUpFileName()
    {
        $path = strtolower($this->path);
        $path = rtrim($path, '/');

        if ($this->israndname) {
            $fileName = $path.'/'.$this->newname;
        } else {
            $fileName = $path.'/'.$this->oldname;
        }

        return $fileName;
    }



    /**
     * 移动文件，暂只支持一个文件或者多个文件移动到一个目标目录中
     * @method moveFile
     * @param  string|array   $file_path 要移动的文件的原路径，如果需要批量移动，此参数传入数组
     * @param  string   $floder    目标目录路径
     * @return boolean
     */
    public function moveFile($file_path, $floder = './')
    {
        if (!file_exists($floder)) {
            mkdir($floder, 0755, true);
        }
        if (is_array($file_path)) {
            $num = count($file_path);
            for ($i = 0; $i < $num; $i++) {
                $img = '.'.ltrim($file_path[$i], '.');
                if (file_exists($img) && is_file($img)) {
                    $r = rename($img, $floder.basename($img));
                    // 如果移动失败，重复一次
                    if ($r === false) {
                        $r = rename($img, $floder.basename($img));
                    }
                }
            }
        } else {
            $img = '.'.ltrim($file_path, '.');
            if (file_exists($file_path) && is_file($file_path)) {
                $r = rename($img, $floder.basename($img));
                // 如果移动失败，重复一次
                if ($r === false) {
                    $r = rename($img, $floder.basename($img));
                }
            }
        }
        return true;
    }






    /**
     * 列出目录下的文件和子目录及其文件
     * @method scandir
     * @param  string  $dir 目录路径
     * @return array  目录下的所有文件
     */
    public function scandir($dir)
    {
        if (function_exists('scandir')) {
            return scandir($dir);
        }   // 有些服务器禁用了scandir
        $dh = opendir($dir);
        $arr = array();
        while ($file = readdir($dh)) {
            if ($file == '.' || $file == '..') {
                continue;
            }
            $arr[] = $file;
        }
        closedir($dh);
        return $arr;
    }




    /**
     * 递归删除目录
     * @method rmovedir
     * @param  string   $dir     要删除的目录路径，可以是绝对路径也可以是相对路径
     * @param  boolean  $keepdir 是否保留当前文件夹，值为 true （默认值） 保留当前文件夹，为 false 不保留
     * @return boolean
     */
    public function rmovedir($dir, $keepdir = true)
    {
        $dir = ltrim($dir, '.');
        // 避免意外删除整站数据
        if (!is_dir($dir) || $dir === '/' || $dir === '/*' || $dir === '../') {
            return false;
        }
        $files = $this->scandir($dir);
        foreach ($files as $file) {
            if ($file == '.' || $file == '..') {
                continue;
            }
            $filepath = $dir.'/'.$file;
            if (is_dir($filepath)) {
                $this->rmovedir($filepath);
            } else {
                @unlink($filepath);
            }
        }
        if (!$keepdir) {
            try {
                rmdir($dir);
            } catch (Exception $e) {
                return $e->getMessages();
            }
        }
        return true;
    }


    public function compress($filename, $size)
    {
        // 压缩图片
        // 根据图片大小决定压缩等级(比例)
        $size_limit_5 = 1024 * 1024 * 5;
        $size_limit_2 = 1024 * 1024 * 2;
        if ($size > $size_limit_5) {
            $percent = 0.5;
        } elseif ($size_limit_2 < $size && $size <= $size_limit_5) {
            $percent = 0.8;
        } else {
            $percent = 1;
        }
        $img = new Image($filename, $percent);
        $img->compressImg($filename);
    }
}
